function TextView(){
    this.$initEventable();
    this.$initRenderable();
    this._textModel = new TextModel();
    this._textLayout = new TextLayout(this._textModel);
    this._annotationModel = new AnnotationModel(this._textModel);
    this.$on(this._textLayout, 'SizeChanged', this._onLayoutSizeChanged);
    this._displayLinesIndexMap = {};
    this._displayLineCount = 0;
    this._pageWidthLimit = 1024;
    this._charWidth = 8;
    this._lineHeight = 20;
}
TextView.prototype = {
    _TPL_ELM: '<div id="#{id}" class="QEView">' +
        '<div class="QETextView">' +
        '<div id="#{id}_scrollBar" class="QEScrollBar"><div id="#{id}_scroll" class="QEScroll"></div></div>' +
        '<div class="QEText">' +
        '<div id="#{id}_content" class="QETextContent"></div>' +
        '</div>' +
        '</div>' +
        '<div class="QELeftRuler"></div>' +
        '<div class="QERightRuler"></div>' +
        '</div>',
    getText: function (startOffset, endOffset){
        return this._textModel.getText(startOffset, endOffset);
    },
    setText: function (text, startOffset, endOffset){
        return this._textModel.setText(text, startOffset, endOffset);
    },
    _getScrollLeft: function (){
        return this.$scrollBarElm().scrollLeft;
    },
    _getScrollTop: function (){
        return this.$scrollBarElm().scrollTop;
    },
    _getClientWidth: function (){
        return this.$scrollBarElm().clientWidth;
    },
    _getClientHeight: function (){
        return this.$scrollBarElm().clientHeight;
    },
    getTextModel: function (){
        return this._textModel;
    },
    getAnnotationModel: function (){
        return this._annotationModel;
    },
    getFirstVisibleDisplayLineIndex: function (){
        return Math.floor(this._getScrollTop() / this._lineHeight);
    },
    getFirstFullVisibleDisplayLineIndex: function (){
        return Math.ceil(this._getScrollTop() / this._lineHeight);
    },
    getVisibleDisplayLineCount: function (){
        return Math.ceil((this._getScrollTop() + this._getClientWidth() / this._lineHeight)) - this.getFirstVisibleDisplayLineIndex();
    },
    getFullVisibleDisplayLineCount: function (){
        return Math.floor((this._getScrollTop() + this._getClientWidth() / this._lineHeight)) - this.getFirstFullVisibleDisplayLineIndex();
    },
    $renderHTML: function (){
        return Utils.format(this._TPL_ELM, { id: this.$id() });
    },
    $scrollElm: function (){
        return this.$elm('scroll');
    },
    $scrollBarElm: function (){
        return this.$elm('scrollBar');
    },
    $contentElm: function (){
        return this.$elm('content');
    },
    $postRender: function (){
        this.$updateScroll();
        this.$redraw();
    },
    $redraw: function (){
        var startLineIndex = this.getFirstVisibleDisplayLineIndex();
        var lineCount = this.getVisibleDisplayLineCount();
        var endLineIndex = startLineIndex + lineCount;
        var scrollLeft = this._getScrollLeft();
        var clientWidth = this._getClientWidth();
        var startPageIndex = Math.floor(scrollLeft / this._pageWidthLimit);
        var endPageIndex = Math.ceil((scrollLeft + clientWidth) / this._pageWidthLimit) + 1;
        var pageCount = endPageIndex - startPageIndex;
        var buff = [];
        for (var pageIndex=startPageIndex; pageIndex<endPageIndex; pageIndex++) {
            buff.push('<div class="QETextPage" style="left:', (pageIndex * this._pageWidthLimit),
                'px;"></div>');
            var leftX = pageIndex * this._pageWidthLimit;
            var rightX = leftX + this._pageWidthLimit;
            for (var lineIndex=startLineIndex; lineIndex<endLineIndex; lineIndex++) {
                var leftOffset = this._textLayout.getOffsetAtPoint([leftX, this._lineHeight * lineIndex]) - 1;
                var rightOffset = this._textLayout.getOffsetAtPoint([rightX, this._lineHeight * lineIndex]) + 1;
                if (this._textLayout.getPointAtOffset(rightOffset)[0] < leftX) {
                    buff.push('<div style="height:20px;"></div>');
                } else {
                    var deltaX = this._textLayout.getPointAtOffset(leftOffset)[0] - leftX;
                    buff.push('<div style="margin-left:', deltaX * this._charWidth,
                        'px;height:', this._lineHeight, 'px;">', this._textLayout.$renderInlineHTML(leftOffset, rightOffset), '</div>')
                }
            }
            buff.push('</div>');
        }
        this.$contentElm().innerHTML = buff.join('');
    },
    $updateScroll: function (){
        this.$scrollElm().style.width = this._textLayout.getWidth() + 'px';
        this.$scrollElm().style.height = this._textLayout.getHeight() + 'px';
    },
    _onLayoutSizeChanged: function (){
        if (this.isRendered()) {
            this.$updateScroll();
        }
    }
};

Utils.extend(TextView, Eventable);
Utils.extend(TextView, Renderable);